/*
 * Copyright (C) 2010 Seapeak.Xu
 *
 * fostmq may be copied only under the terms of the GNU General
 * Public License V3, which may be found in the fostmq source kit.
 *
 * Created on: 2010-8-3
 * Author: Seapeak.Xu
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <bits/time.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>

#include "fost_sock.h"
#include "fost_logger.h"
#include "fost_typedef.h"
#include "fost_notify.h"
#include "fost_event.h"
#include "fost_mgnb.h"
#include "fost_mem.h"
#include "fost_taskpool.h"

static int set_socket_keepalive(int sock);
static int set_socket_reuseaddr(int sock);
static int set_tcp_keepalive(int sock, size_t timeout);
static int set_socket_linger(int sock, size_t timeout);
static int set_tcp_nodelay(int sock);
static int set_socket_timout(int sock, size_t timeout);
static int socket_init(const char *ip, const int port, size_t timeout,
		struct sockaddr_in *addr,bool iskeepalive, bool islinger,
		bool isnodelay,bool istimeout);
static int set_sock(int sock, uint timeout, bool iskeepalive, bool islinger,
		bool isnodelay,bool istimeout);


int server_sock_init(const char *ip, const int port, const size_t listens,
		const size_t timeout)
{
	struct sockaddr_in addr;
	int result = 0;
	int sock;

	sock = socket_init(ip, port, timeout, &addr, true, true, true, false);
	if (-1 == sock)
	{
		log_error("File:%s,Line:%d,Function:%s.init the sockfd is error.",
				__FILE__, __LINE__, __FUNCTION__);
		return -1;
	}
	if (-1 == bind(sock, (struct sockaddr *) &addr,sizeof(struct sockaddr_in)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.bind socket address to socket is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		close(sock);
		return -1;
	}

	if (-1 == listen(sock, listens))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Listen the socket is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		close(sock);
		return -1;
	}
	return sock;
}

int client_sock_init(const char *ip, const int port, const size_t timeout,bool isnonblocking)
{
	struct sockaddr_in addr;
	int sock;
	int result = 0;
	sock = socket_init(ip, port, timeout, &addr, true, true, true, false);
	if (-1 == sock)
	{
		log_error("File:%s,Line:%d,Function:%s.init the sockfd is error.",
				__FILE__, __LINE__, __FUNCTION__);
		return -1;
	}
	
	if (-1 == connect(sock,&addr,sizeof(addr)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.init the sockfd is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}

	if(isnonblocking)
	{
		if( -1 == set_nonblocking(sock))
		{
			return -1;
		}
	}

	return sock;
}

static int socket_init(const char *ip, const int port, size_t timeout,
		struct sockaddr_in *addr,bool iskeepalive, bool islinger,
		bool isnodelay,bool istimeout)
{
	int sock;
	int result = 0;
	if (-1 == (sock = socket(AF_INET, SOCK_STREAM, 0)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.create socket fd is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}

	if (-1 == set_socket_reuseaddr(sock))
	{
		close(sock);
		return -1;
	}

	bzero(addr, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_port = htons(port);
	if (NULL == ip || '\0' == *ip || 0 == strlen(ip))
	{
		addr->sin_addr.s_addr = htonl(INADDR_ANY);
	}
	else
	{
		if (0 == inet_aton(ip, &(addr->sin_addr)))
		{
			result = errno;
			log_error("File:%s,Line:%d,Function:%s.Convesrt ip to net binary data is error.Errno:%d,Info:%s.",
					__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
			close(sock);
			return -1;
		}
	}

	if (-1 == set_sock(sock, timeout, iskeepalive, islinger, isnodelay,istimeout))
	{
		close(sock);
		return -1;
	}
	return sock;
}

int set_nonblocking(int sock)
{
	int flags;
	int result = 0;
	if (-1 == (flags = fcntl(sock, F_GETFL)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.get socket nonblocking state is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	flags |= O_NONBLOCK;
	if (-1 == fcntl(sock, F_SETFL, flags))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.set socket nonblocking state is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	return result;
}

static int set_socket_keepalive(int sock)
{
	int optval = 1;
	int result = 0;
	if (-1 == setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval,
				(socklen_t) sizeof(int)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set socket keep alive is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	return result;
}
static int set_socket_reuseaddr(int sock)
{
	int optval = 1;
	int result = 0;
	if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval,
				(socklen_t) sizeof(int)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set socket reuseadd is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__,result, strerror(result));
		return -1;
	}
	return result;
}

static int set_tcp_keepalive(int sock, size_t keepidle)
{
	int result = 0;
	if (-1 == set_socket_keepalive(sock))
	{
		return -1;
	}

	if (-1 == setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &keepidle,sizeof(keepidle)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set tcp keepalive is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	int keepcnt = 3;
	if (-1 == setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set the tcp keepcnt is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	int keepintvl = 10;
	if (-1 == setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &keepintvl,sizeof(keepintvl)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set the tcp keepintvl is error.Errno:%d,Info%s.",
				__FILE__, __LINE__, __FUNCTION__,result, strerror(result));
		return -1;
	}

	return 0;
}
static int set_socket_linger(int sock, size_t timeout)
{
	struct linger optval;
	optval.l_onoff = true;
	optval.l_linger = timeout;
	int result = 0;
	if (-1 == setsockopt(sock, SOL_SOCKET, SO_LINGER, &optval,
				(socklen_t) sizeof(struct linger)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set socket linger is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	return 0;
}

static int set_tcp_nodelay(int sock)
{
	int optval = 1;
	int result = 0;
	if (-1 == setsockopt(sock, SOL_TCP, TCP_NODELAY, &optval, sizeof(optval)))
	{
		log_error("File:%s,Line:%d,Function:%s.set tcp nodelay is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__,result, strerror(result));
		return -1;
	}
	return 0;
}

static int set_socket_timout(int sock, size_t timeout)
{
	struct timeval waittime;
	waittime.tv_sec = timeout;
	waittime.tv_usec = 0;
	int result = 0;
	if (-1 == setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &waittime,
				sizeof(struct timeval)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set socket send timeout is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__,result, strerror(result));
		return -1;
	}
	if (-1 == setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &waittime,
				sizeof(struct timeval)))
	{
		result = errno;
		log_error("File:%s,Line:%d,Function:%s.Set socket recv timeout is error.Errno:%d,Info:%s.",
				__FILE__, __LINE__, __FUNCTION__, result,strerror(result));
		return -1;
	}
	return 0;
}


int sock_accept(int sock, int threadpools)
{
	for (;;)
	{
		if (!g_loop)
			break;
		do
		{
			struct sockaddr_in client_addr;
			unsigned int socket_len;
			int client_sock;

			socket_len = sizeof(struct sockaddr_in);
			client_sock = accept(sock, (struct sockaddr *) &client_addr,&socket_len);
			if (0 > client_sock)
			{
				if(EINTR == errno || EAGAIN == errno)
				{
					continue;
				}
				log_error("File:%s,Line:%d,Function:%s.accept the sock is error.Errno:%d,Info:%s.Error sockfd:%d.",
						__FILE__,__LINE__,__FUNCTION__,errno,strerror(errno),client_sock);
				continue;
			}

			if(0 == client_sock)
			{
				log_error("File:%s,Line:%d,Function:%s.get the client sockfd is %d.",
						__FILE__,__LINE__,__FUNCTION__,client_sock);
				continue;
			}

			char client[IP_LEN + 1 + 10];
			memset(client,'\0',IP_LEN + 1 + 10);
			snprintf(client,IP_LEN + 1 + 10,"%s:%d",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

			notify_elt_t *notify_elt = g_notify_elts + client_sock % threadpools;
			
			if(sizeof(client_sock) != write(notify_elt->pipe_fds[1],&client_sock,sizeof(client_sock)))
			{
				close(client_sock);
				log_error("File:%s,Line:%d,Function:%s.send the task addr to working thread is fail.",
				__FILE__,__LINE__,__FUNCTION__);
				return -1;
			}

			
		}while(false);
	}
	return 0;
}

static int set_sock(int sock, uint timeout, bool iskeepalive, bool islinger,
		bool isnodelay,bool istimeout)
{
	int result = 0;
	if (iskeepalive)
	{
		result = set_tcp_keepalive(sock, timeout);
		if (-1 == result)
			return result;
	}
	if (islinger)
	{
		result = set_socket_linger(sock, timeout);
		if (-1 == result)
			return result;
	}
	if (isnodelay)
	{
		result = set_tcp_nodelay(sock);
		if (-1 == result)
			return result;
	}
	if(istimeout)
	{
		result = set_socket_timout(sock, timeout);
		if(-1 == result)
			return result;
	}
	return result;
}

